package com.rc.saas.tenant.interceptor;

import com.rc.saas.tenant.common.constant.ShiroConstants;
import com.rc.saas.tenant.interceptor.parser.SqlParserHandler;
import com.rc.saas.tenant.mapper.tenant.TenantMapper;
import com.rc.saas.tenant.model.tenant.TenantUser;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import java.util.Properties;

/**
 * 1 Executor
 * (update, query, flushStatements, commit,  rollback, getTransaction, close, isClosed)
 *
 * 2 ParameterHandler
 * (getParameterObject, setParameters)
 *
 * 3 StatementHandler
 * (prepare, parameterize, batch, update, query)
 *
 * 4 ResultSetHandler
 * (handleResultSets, handleOutputParameters)
 *
 * 查询拦截器
 * Created by sven on 2019/1/11
 */
//@Component
@Intercepts({
        @Signature(type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class TenantInterceptor2 implements Interceptor {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private TenantMapper tenantMapper;

    private ApplicationContext ac;

    // 针对不同查询操作的处理类

    //private CreateProcess createProcess;
    //private DeleteProcess deleteProcess;
    //private InsertProcess insertProcess;
    //private UpdateProcess updateProcess;

    //@Autowired
    private SqlParserHandler sqlParserHandler;

    public SqlParserHandler getSqlParserHandler() {
        return sqlParserHandler;
    }

    public void setSqlParserHandler(SqlParserHandler sqlParserHandler) {
        this.sqlParserHandler = sqlParserHandler;
    }
//@Autowired
    //private ApplicationContextUtil springApplicationContextUtil;

    //@Autowired
    //public void setSqlParserHandler(SqlParserHandler sqlParserHandler) {
    //    this.sqlParserHandler = sqlParserHandler;
    //}

    /**
     * intercept方法就是要进行拦截的时候要执行的方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (ContextLoader.getCurrentWebApplicationContext() == null) {
            return invocation.proceed();
        }

        ServletContext sc = ContextLoader.getCurrentWebApplicationContext().getServletContext();
        ac = WebApplicationContextUtils.getWebApplicationContext(sc);

        //ApplicationContext ac = springApplicationContextUtil.getApplicationContext();
        sqlParserHandler = (SqlParserHandler) ac.getBean("sqlParserHandler");

        //sqlParserHandler = SpringApplicationContextUtil.getBean("sqlParserHandler");
        //if(springApplicationContextUtil==null){
        //    return invocation.proceed();
        //}
        //sqlParserHandler = (SqlParserHandler)springApplicationContextUtil.getBean("sqlParserHandler");

        // 根据签名指定的args顺序获取具体的实现类
        // 1. 获取MappedStatement实例, 并获取当前SQL命令类型
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        SqlCommandType commandType = ms.getSqlCommandType();

        // 2. 获取当前正在被操作的类, 有可能是Java Bean, 也可能是普通的操作对象, 比如普通的参数传递
        // 普通参数, 即是 @Param 包装或者原始 Map 对象, 普通参数会被 Mybatis 包装成 Map 对象
        // 即是 org.apache.ibatis.binding.MapperMethod$ParamMap
        Object parameter = invocation.getArgs()[1];
        // 获取拦截器指定的方法类型, 通常需要拦截 update
        String methodName = invocation.getMethod().getName();
        logger.info("查询拦截器, methodName; {}, commandType: {}", methodName, commandType);

        // 3. 获取当前用户信息
        TenantUser user = null;
        try {
            Session session = SecurityUtils.getSubject().getSession();
            user = (TenantUser) session.getAttribute(ShiroConstants.SESSION_TENANT_USER_KEY);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }

        if (user != null) {
            //TODO 如果是selectByExample方法，WHERE增加TENANT_CODE条件

            //TODO 如果是手工的SQL，如果没有TENANT_CODE就报错 ，不反加数据

            if (methodName.equals("query") && commandType.equals(SqlCommandType.SELECT)) {
                //if (commandType.equals(SqlCommandType.SELECT)) {
                // 拦截sql
                Object[] args = invocation.getArgs();
                MappedStatement statement = (MappedStatement) args[0];
                Object parameterObject = args[1];
                BoundSql boundSql = statement.getBoundSql(parameterObject);
                String sql = boundSql.getSql();
                logger.info("=========== 获取到的SQL ===========" + "\r\n" + sql);
                //logger.info(sql);

                //jsqlparse where 解析器改写
                String newSql = sqlParserHandler.parseSql(sql, user.getTenantCode());

                logger.info("=========== 更改后到的SQL ===========" + "\r\n" + newSql);
                //statementHandler.setValue("delegate.boundSql.sql", newSql);

                //更新SQL
                ReflectUtil.setFieldValue(boundSql, "sql", newSql);
                //}
            }

            if (invocation.getTarget() instanceof RoutingStatementHandler) {
                RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
                StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
                //通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
                MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
                //千万不能用下面注释的这个方法，会造成对象丢失，以致转换失败
                //MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
                //PermissionAop permissionAop = PermissionUtils.getPermissionByDelegate(mappedStatement);
                //if(permissionAop == null){
                //    if(log.isInfoEnabled()){
                //        log.info("数据权限放行...");
                //    }
                //    return invocation.proceed();
                //}
                //if(log.isInfoEnabled()){
                //    log.info("数据权限处理【拼接SQL】...");
                //}
                //BoundSql boundSql = delegate.getBoundSql();
                //ReflectUtil.setFieldValue(boundSql, "sql", permissionSql(boundSql.getSql()));
            }

            //throw new RuntimeException("Prohibit the use of SQL statements without where conditions.originSql"+originSql);
        } else {
            logger.info("尚未登录...");
        }

        //继续执行
        return invocation.proceed();
    }

    /**
     * plugin方法中我们可以决定是否要进行拦截
     * @param o
     * @return
     */
    @Override
    public Object plugin(Object o) {
        //拦截 return Plugin.wrap(target, this);
        //不拦截 return target;

        return Plugin.wrap(o, this);
    }

    /**
     * 在Mybatis配置文件中指定一些属性
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
    }


}
